/**
 * Image Capture API
 * 该 API 定义了使用 MediaStreamTrack 接口从摄影设备中捕获图片资源的能力。
 * 被捕获的图片可以在 Form 表单中作为 Blob 对象使用，也可以作为 ImageBigmap 使用。
 * 读取能力和设置以及应用约束是通过两种方法之一实现的，这取决于它是否影响视频 MediaStreamTrack。
 * 照片指定能力和当前设置可以通过 getPhotoCapabilities()/getPhotoSetting() 方法恢复。
 * 通过 takePhoto() 的 PhotoSettings 参数进行配置。
 * 操作视频相关能力，当前设置和约束是通过 MediaStreamTrack 扩展实现的。
 */

/**
 * Image Capture API 中的接口实现是基于 Promise 的。
 * ImageCapture(MediaStreamTrack videoTrack) videoTrack 作为资源数据保存在 track 字段中。要求 kind 参数必要。
 * ImageCapture 字典： {
 *    track: MediaStreamTrack 类型，只读，保存构造函数中传入的 MediaStreamTrack 参数
 *    takePhoto(optional PhotoSettings photoSettings): 生产视频捕获设备单次曝光的结果，和轨道以及所有 PhotoSettings 配置。如果成功，返回一个 Blob 编码的图片资源。
 *    getPhotoCapabilities(): 返回 Promise<PhotoCapabilities>， 用于检索可用配置选项的范围
 *    getPhotoSettings(): 返回 Promise<PhotoSettings>，用于检索当前配置设置值
 *    grabFrame(): 获取一个轨道中的现场视频的快照，返回一个 返回 Promise<ImageBitmap> 。
 * }
 * 
 * getPhotoCapabilities 函数返回的数据类型
 * PhotoCapabilities 字典： {
 *    redEyeReduction: RedEyeReduction 类型，只读，红眼消除能力
 *    imageHeight: MediaSettingsRange 类型，只读，UA 提供的图片高度支持的映射
 *    imageWidth: MediaSettingsRange 类型，只读，UA 提供的图片宽度支持的映射
 *    fillLightMode: FrozenArray<FillLightMode>，只读，如果存在的话是 fill light mode 支持的映射
 * }
 * 
 * takePhoto 函数调用时出入的参数对象
 * PhotoSettings 字典： {
 *    redEyeReduction: {Boolean} 反映是否要求消除红眼
 *    imageHeight: {double} 反映是否要求图片高度，UA 必须选择一个最接近的高度值
 *    imageWidth: {double} 反映是否要求图片的宽度，UA 必须选择一个最接近的宽度值
 *    fillLightMode: {FillLightMode} 反映是否要求填充亮度模型
 * }
 * 
 * PhotoCapabilities 反映图片长宽的数据类型
 * MediaSettingsRange 字典： {
 *    max: {double}，只读，设置的最大值
 *    min: {double}，只读，设置的最小值
 *    step: {double}，只读，设置的连续值之间的最小差值
 * }
 * 
 * enum RedEyeReduction 字典： {
 *    "never": 不在设备中启动红眼消除
 *    "always": 总是启用红眼消除
 *    "controllable": 启用红眼消除，但用户可以通过 redEyeReduction 控制
 * }
 * 
 * enum FillLightMode 字典： {
 *    "auto": 需要时允许视频设备填充亮度。否则应该不允许。
 *    "off": 资源的填充亮度和闪光灯不允许使用
 *    "falsh": 总是允许 takePhoto 方法调用时启用闪光灯
 * }
 * 
 * 上面的所有数据结构是 Image Capture API 的基础支持，在其上，规格还为 MediaStreamTrack 对象定义了扩展，让它的各种行为更适合图片捕获。
 * 主要扩展的是 getCapabilities、getSettings、getConstraints、applyConstraints 方法，均挂载在 MediaStreamTrack 对象上
 * MediaStreamTrack 对象的来源通常是 Stream 对象调用对应的 get Track 方法（getVideoTracks()[0]）
 * Stream 定义在 Media Capture and Stream API 规格中。
 * 
 * MediaTrackSupportedConstraints 接口扩展了一组约束，UA 必须识别这些约束用于控制照相能力。
 * MediaTrackSupportedConstraints 字典： {
 *    whiteBalanceMode, colorTemperature（色温）, exposureMode, exposureCompensation（曝光补偿）,
 *    iso, focusMode, pointOfInterest（计量中心点）, brightness（亮度）, contrast（对比度）, 
 *    saturation（饱和度），sharpness（锐度），focusDistance，zoom（变焦），torch
 * }
 * 上面各成员分别指示是否识别对应的约束
 * 
 * MediaTrackCapabilities 接口扩展了图片捕获时的特殊能力，与 MediaTrackSupportedConstraints 中的约束一一对应
 * 这一扩展来自于 Media Capture API，定义的 Track 对象，通过调用 getCapabilities 方法获得。
 * MediaTrackCapabilities 字典： {
 *    whiteBalanceMode: {Array<MeteringMode>} 受支持的白平衡模型数组，每个成员都是 MeteringMode 的字段
 *    colorTemperature: {MediaSettingsRange} 受支持的用于场景白平衡计算的色温范围
 *    exposureMode: {Array<MeteringMode>} 受支持的曝光模式数组，每个成员都是 MeteringMode 的字段
 *    exposureCompensation: {MediaSettingsRange} 受支持的曝光补偿范围
 *    iso: {MediaSettingsRange} 感光度范围
 *    focusMode: {Array<MeteringMode>} 受支持的对焦模式数组，每个成员都是 MeteringMode 的字段
 *    brightness: {MediaSettingsRange} 受支持的亮度范围，值越大亮度越大
 *    contrast: {MediaSettingsRange} 受支持的对比度范围
 *    saturation: {MediaSettingsRange} 受支持的饱和度范围
 *    sharpness: {MediaSettingsRange} 受支持的锐度范围
 *    focusDistance: {MediaSettingsRange} 反映的是 UA 支持的焦距
 *    zoom: {MediaSettingsRange} 反映的是 UA 支持的伸缩范围
 *    torch: {boolean} 反映相机是否支持火炬模式
 * }
 * 
 * enum MeteringMode 字典： {
 *    "none": 当前源不支持 对焦/曝光/白平衡 模型，可以理解成关闭对应的特性
 *    "manual": 捕获设备被设置为手动控制镜头位置/曝光时间/白平衡，或要求配置这样的模式。
 *    "single-shot": 捕获设备被配置为单次扫描对焦/单次曝光/白平衡计算，或者要求配置这样的模式。
 *    "continuous": 捕获设备被配置为近零延迟的连续快门/连续自动曝光/白平衡计算，或者要求这样连续对焦/曝光/白平和计算的模式
 * }
 * 
 * MediaTrackConstraintSet 接口字典既用于 getConstraints() 读取当前状态，也用于 pplyConstraints() 
 * MediaTrackConstraintSet 字典： {
 *    成员字段与 MediaTrackSupportedConstraints 一样，字段类型是：
 *      ConstrainString、ConstrainDouble、ConstrainBoolean、ConstrainPoint2D 其中之一
 * }
 * 
 * MediaTrackSettings 当在一个视频流上调用 getSettings 方法时，用户代理必须返回一个扩大的 MedisTrackSettings 对象，代表当前用户代理状态
 * MediaTrackSettings 字典： {
 *    成员与 MediaTrackSupportedConstraints 一样，字段类型是基础类型与 MediaTrackConstraintSet 对应。
 * }
 */
// 控制文档模块显示
const ctrlDomDisplay = (module) => {
  if(module) {
    let oUsageBlocks = document.getElementsByClassName('usageBlock');
    let oContainer = document.querySelector(`#${module}`);
    for(let i = 0; i < oUsageBlocks.length; i += 1) {
      oUsageBlocks[i].style.display = 'none';
    }
    oContainer.style.display = 'block';
  }
}

/**
 * 使用 takePhoto() 进行单次拍照
 */
const UsageTakePhoto = (module) => {
  ctrlDomDisplay(module)
  // 主要内容
  let imageCapture;

  const gotMedia = (mediastream) => {
    let video = document.querySelector("#photoVideo");
    video.srcObject = mediastream;
    const track = mediastream.getVideoTracks()[0];
    imageCapture = new ImageCapture(track);

    const capabilities = track.getCapabilities();
    // 检查是否支持伸缩能力
    console.log(capabilities);
    if(!capabilities.zoom) { return } // 不支持则结束

    // 将伸缩能力映射到滑动元素上
    const input = document.querySelector('#photoImg');
    input.min = capabilities.zoom.min;
    input.max = capabilities.zoom.max;
    input.step = capabilities.zoom.step;
    input.value = track.getSettings().zoom;

    input.oninput = event => {
      track.applyConstraints({
        advanced: [{ zoom: event.targe.value }]
      })
    }
    input.hidden = false;
  }

  // 拍照函数
  const takePhoto = () => {
    imageCapture.takePhoto()
      .then(blob => {
        console.log(`Photo taken: ${blob.type}, ${blob.size} B`);
        alert(`Photo taken: ${blob.type}, ${blob.size} B`);

        const image = document.querySelector('#photoImg');
        image.src = URL.createObjectURL(blob);
      })
      .catch(e => {
        console.error('takePhoto() failed: ' + e.message);
        alert('takePhoto() failed: ' + e.message);
      });
  }
  
  // 事件绑定
  document.querySelector('#takePhoto').addEventListener('click', () => {
    takePhoto();
  })

  // 获取设备流数据
  navigator.mediaDevices.getUserMedia({
    video: true
  }).then(gotMedia)
  .catch(e => {
    console.error(`getUserMedia() failed: ${e.message}`);
    alert(`getUserMedia() failed: ${e.message}`);
  });
}

/**
 * 使用 grabFrame() 重复连续抓取帧
 */
const usageGrabFrame = (module) => {
  ctrlDomDisplay(module)
  // 主要内容
  let canvas = document.querySelector('#grabCanvas');

  let interval, track;

  // 帧图像处理
  const processFrame = (imgData) => {
    canvas.width = imgData.width;
    canvas.height = imgData.height;
    canvas.getContext('2d').drawImage(imgData, 0, 0);
  }

  // 获取媒体流
  const gotMedia = (mediastream) => {
    track = mediastream.getVideoTracks()[0];
    let imageCapture = new ImageCapture(track);
    interval = setInterval(() => {
      imageCapture.grabFrame()
        .then(processFrame)
        .catch(e => {
          console.log(`grabFrame() failed: ${e.message}`);
          alert(`grabFrame() failed: ${e.message}`);
        })
    }, 1000);
  }

  // 停止获取帧图片
  const stopGrabFrame = () => {
    clearInterval(interval);
    track.stop();
  }

  // 事件绑定
  document.querySelector('#stopGrab').addEventListener('click', stopGrabFrame);

  navigator.mediaDevices.getUserMedia({ video: true})
    .then(mediastream => {
      gotMedia(mediastream);
    }).catch(e => {
      console.error(`getUserMedia() failed: ${e.message}`);
      alert(`getUserMedia() failed: ${e.message}`);
    })
}

/**
 * 获取帧图片和后处理
 */
const grabFrameAndPostProcessing = (module) => {
  ctrlDomDisplay(module);
  // 主要内容
  let canvas = document.querySelector('#ppCanvas');
  let track;

  // 处理媒体帧
  const processFram = (imageBitmap) => {
    track.stop();
    // ImageBitmap 的像素数据是不能直接访问的，需要通过 canvas 绘制一次，然后使用 getImageData() 方法获取
    let ctx = canvas.getContext('2d');
    canvas.width = imageBitmap.width;
    canvas.height = imageBitmap.height;
    ctx.drawImage(imageBitmap, 0, 0);
    
    // 读取像素数据并转换色彩
    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    let data = imageData.data;
    for(let i = 0; i < data.length; i += 4) {
      data[i]     = 255 - data[i]; // red
      data[i + 1] = 255 - data[i + 1]; // green
      data[i + 2] = 255 - data[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  }

  // 从媒体流中截取帧
  const gotMedia = (mediastream) => {
    track = mediastream.getVideoTracks()[0];
    let imageCapture = new ImageCapture(track);
    imageCapture.grabFrame()
      .then(processFram)
      .catch(e => {
        console.log(`grabFrame() failed: ${e.message}`);
        alert(`grabFrame() failed: ${e.message}`);
      })
  }

  navigator.mediaDevices.getUserMedia({ video: true })
    .then(gotMedia)
    .catch(e => {
      console.log(`getUserMedia() failed: ${e.message}`);
      alert(`getUserMedia() failed: ${e.message}`);
    })
}

/**
 * 通过 range 控件手动对焦，并拍照
 */
const focusAndTakePhoto = (module) => {
  ctrlDomDisplay(module);
  // 主要内容
  let imageCapture;

  // 处理媒体流
  const gotMedia = (mediastream) => {
    let oVideo = document.querySelector('#fapVideo');
    oVideo.srcObject = mediastream;

    let track = mediastream.getVideoTracks()[0];
    imageCapture = new ImageCapture(track);

    let capabilities = track.getCapabilities();
    console.log(capabilities);
    if(!capabilities.focusDistance) { return; }

    // 将焦距与range控件绑定
    const input = document.querySelector("#fapRange");
    input.min = capabilities.focusDistance.min;
    input.max = capabilities.focusDistance.max;
    input.step = capabilities.focusDistance.step;
    input.value = track.getSettings().focusDistance

    input.addEventListener('input', () => {
      track.applyConstraints({
        advanced: [{ focusMode: "manual", focusDistance: event.target.value }]
      })
    })
    input.hidden = false;
  }

  // 绑定拍照按钮事件
  document.querySelector("#fapTakePhoto").addEventListener('click', () => {
    imageCapture.takePhoto()
      .then(blob => {
        console.log(`Photo taken: ${blob.type}, ${blob.size}B`);
        let image = document.querySelector("#fapImg");
        image.src = URL.createObjectURL(blob);
      })
      .catch(e => {
        console.log(`takePhoto() failed: ${e.message}`);
        alert(`takePhoto() failed: ${e.message}`);
      })
  })

  navigator.mediaDevices.getUserMedia({ video: true })
    .then(gotMedia)
    .catch(e => {
      console.log(`getUserMedia() failed: ${e.message}`);
      alert(`getUserMedia() failed: ${e.message}`);
    })
}

UsageTakePhoto('takePhoto');
// usageGrabFrame('grabFrame')
// grabFrameAndPostProcessing('postProcessing');
// focusAndTakePhoto('focusAndPhoto')